﻿// 
//    Copyright 2010 The JaernCloud Project
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JaernCloud.Language.Compiler.Types;
using System.Dynamic;
using System.Diagnostics;

namespace JaernCloud.Language.Runtime {
    
    /// <summary>
    /// Default implementation of INodeObject
    /// </summary>
    public class NodeObject<T> : DynamicObject, INodeObject<T>, IQueryResultNodeObject where T: INodeType {

        private static List<object> _no_values = new List<object>(0);
        
        protected T _type;
        protected INodeObjectGenerator _objectGenerator;
        private INodeObject<INodeType> _parent;
        private IEnumerable<INodeObject<INodeType>> _children;
        private IEnumerable<INodeObject<INodeType>> _inheritsFrom;

        readonly int _id = NodeObjectExtensions.NewId;
        private List<string> _names;
        private List<object> _values;
        public int Id { get { return _id; }}

        public T Type {
            get { return _type; }
        }

        public virtual INodeObject<T> FromType(INodeType type, INodeObjectGenerator objectGenerator) {
            return new NodeObject<T> { _type = (T)type, _objectGenerator = objectGenerator };
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            foreach (var child in Children) {
                if (child.Name == binder.Name) {
                    result = child;
                    return true;
                }
            }
            return base.TryGetMember(binder, out result);
        }

        public virtual IEnumerable<INodeObject<INodeType>> Children {
            get {
                if (_children == null) {
                    var children = new List<INodeObject<INodeType>>();
                    foreach (INodeType nodeType in _type.Children) {

                        INodeObject<INodeType> child = null;
                        foreach (var inherited in InheritsFrom) {
                            foreach (var inheritedChild in inherited.Children) {
                                if (inheritedChild.Type.Equals(nodeType)) {
                                    child = inheritedChild;
                                    break;
                                }
                            }
                            if (child != null) break;
                        }
                        if(child == null) {
                            // no inherited child was found, so create an instance for it
                            child = _objectGenerator.GetNodeObjectInstanceFor(nodeType);
                        }
                        if (child.Parent == null) {
                            // child is not inherited and has no parent yet, so set the parent to this node object
                            child.Parent = this as INodeObject<INodeType>;
                        }
                        children.Add(child);
                    }
                    _children = children;
                }
                return _children;
            }
        }

        public virtual IEnumerable<INodeObject<INodeType>> InheritsFrom {
            get {
                if (_inheritsFrom == null) {
                    var inheritsFromList = new List<INodeObject<INodeType>>();
                    foreach (INodeType nodeType in _type.InheritsFrom) {
                        var inheritsFrom = _objectGenerator.GetNodeObjectInstanceFor(nodeType);
                        if (inheritsFrom is IHasValueHolder && this is INodeObjectValueHolder) {
                            (inheritsFrom as IHasValueHolder).ValueHolder = this as INodeObjectValueHolder;
                        }
                        inheritsFromList.Add(inheritsFrom);
                    }
                    _inheritsFrom = inheritsFromList;
                }
                return _inheritsFrom;
            }
        }

        public INodeObject<INodeType> Parent {
            get {
                return _parent;
            }
            set {
                _parent = value;
            }
        }

        public virtual string Name {
            get { return _type.Name; }
        }

        private void AddIdentifier(string identifier) {
            if (!string.IsNullOrEmpty(identifier)) {
                var names = Names; // initalize the collection
                if (!_names.Contains(identifier)) {
                    _names.Add(identifier);
                }
            }
        }

        public virtual IEnumerable<string> Names {
            get {
                if (_names == null) {
                    _names = new List<string>();
                    _names.AddRange(_type.Names);
                }
                return _names;
            }
        }

        public IEnumerable<object> Values {
            get {
                if (_values == null) return _no_values;
                return _values;
            }
        }

        public override string ToString() {
            return "[" + Id + "] " + this.GetType().Name + "(" + _type.ToString() + ")";
        }


        #region IQueryResultNodeObject Members

        public void AddValue(object value, string identifier = null) {
            if (_values == null) {
                _values = new List<object>();
                _values.Add(value);
                AddIdentifier(identifier);
            }
        }

        #endregion
    }
}
